﻿using System.Reflection.Emit;
using System;
using System.Reflection;
using System.Linq;
using ProxyViewModel.Commands;
using System.Windows.Input;
using System.Collections.Generic;
namespace ProxyViewModel.Emitors {
	internal class CommandsEmitor : EmitorBase {
		public CommandsEmitor(TypeBuilder typeBuilder, Type operatable, IDictionary<string, object> parameters)
			: base(typeBuilder, operatable, parameters) { }

		protected override void EmitContent() {
			MethodInfo[] potentialCommands = ReflectionHelper.GetMethodsByNameMatch(Operatable, "^(?!CanExecute).+Command$");
			IDictionary<string, FieldInfo> _commandsToInitialize = new Dictionary<string, FieldInfo>();
			potentialCommands.Where(e => e.ReturnType == typeof(void) &&
				// commands can have only one or zero parameters
				e.GetParameters().Length <= 1).Select(e => e).ToList().ForEach(e => {
					FieldInfo commandField = BuildCommandProperty(e);
					_commandsToInitialize[e.Name] = commandField;
				});
			GenerateInitializeFunction(_commandsToInitialize);
		}

		private void GenerateInitializeFunction(IDictionary<string, FieldInfo> commandsToInitialize) {
			MethodBuilder methodBuilder = TypeBuilder.DefineMethod("CommandsInitialization", MethodAttributes.Private);
			ILGenerator il = methodBuilder.GetILGenerator();
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Nop);
			foreach (KeyValuePair<string, FieldInfo> methodNameAndCommand in commandsToInitialize)
			{
				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldstr, methodNameAndCommand.Key);
				il.Emit(OpCodes.Newobj, typeof(ProxyCommand).GetConstructor(new Type[] { typeof(object), typeof(string) }));
				il.Emit(OpCodes.Stfld, methodNameAndCommand.Value);
			}
			il.Emit(OpCodes.Ret);
			CallMethodOnConstruction(methodBuilder);
		}

		private FieldInfo BuildCommandProperty(MethodInfo executeMethod) {
			string propertyName = executeMethod.Name.Replace("Command", "");
			FieldBuilder field = DefineField(propertyName);
			PropertyBuilder propertyBuilder = TypeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, typeof(ICommand), new Type[] { typeof(ICommand) });
			propertyBuilder.SetGetMethod(BuildGetter(propertyName, executeMethod.Name, field));
			return field;
		}

		private FieldBuilder DefineField(string propertyName) {
			return TypeBuilder.DefineField(string.Format("_{0}", propertyName), typeof(ICommand), FieldAttributes.Private);
		}

		private MethodBuilder BuildGetter(string commandName, string executeMethodName, FieldBuilder field) {
			MethodBuilder method = TypeBuilder.DefineMethod(string.Format("get_{0}", commandName), MethodAttributes.Public |
				MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(ICommand), Type.EmptyTypes);
			ILGenerator il = method.GetILGenerator();

			il.DeclareLocal(typeof(ProxyCommand));
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, field);
#if !SILVERLIGHT
			il.Emit(OpCodes.Stloc_0);
			Label lbl = il.DefineLabel();
			il.Emit(OpCodes.Br_S, lbl);
			il.MarkLabel(lbl);
			il.Emit(OpCodes.Ldloc_0);
#endif
			il.Emit(OpCodes.Ret);

			return method;
		}
	}
}
